#include <GameEngine/Graphics/Lighting/light.h>

#include <GameEngine/Graphics/openGL.h>


#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=====================================================================

Q_DECLARE_OPERATORS_FOR_FLAGS(Light::LightParameters)

//=================================================================================

//! Default constructor
/*!
 *  The position is defaulted to (0,0,0), the ambient color to black, the diffuse and
 *  specular colors to white, and the light is setup as positional. By default, the
 *  ambient, diffuse and specular contributions of the light are activated.
 */
Light::Light() : position_(0,0,0), infinite_(false),
	acolor_(0.0f,0.0f,0.0f,1.0f), dcolor_(1.0f,1.0f,1.0f,1.0f), scolor_(1.0f,1.0f,1.0f,1.0f)
{
	set_active_parameter(AmbientLight);
	set_active_parameter(DiffuseLight);
	set_active_parameter(SpecularLight);
}

//! Copy constructor
/*!
 @param rhs The light to copy
 */
Light::Light(const Light& rhs) : position_(rhs.position_), infinite_(rhs.infinite_),
	parameters_(rhs.parameters_),
	acolor_(rhs.acolor_), dcolor_(rhs.dcolor_), scolor_(rhs.scolor_),
	spot_direction_(rhs.spot_direction_), spot_exponent_(rhs.spot_exponent_), spot_cutoff_(rhs.spot_cutoff_),
	constant_attenuation_(rhs.constant_attenuation_), linear_attenuation_(rhs.linear_attenuation_),
	quadratic_attenuation_(rhs.quadratic_attenuation_) {}

//! Destructor
Light::~Light() {}

//! Assignment operator
/*!
 @param rhs The light to copy
 */
Light& Light::operator=(const Light& rhs) {
	if ( &rhs != this ) {
		position_ = rhs.position_;
		infinite_ = rhs.infinite_;
		parameters_ = rhs.parameters_;
		acolor_ = rhs.acolor_;
		dcolor_ = rhs.dcolor_;
		scolor_ = rhs.scolor_;
		spot_direction_ = rhs.spot_direction_;
		spot_exponent_ = rhs.spot_exponent_;
		spot_cutoff_ = rhs.spot_cutoff_;
		constant_attenuation_ = rhs.constant_attenuation_;
		linear_attenuation_ = rhs.linear_attenuation_;
		quadratic_attenuation_ = rhs.quadratic_attenuation_;
	}
	return *this;
}

//! Accessor to the position of the light (read only)
/*!
 @return A constant reference to the position of the light
 */
const Point3d<float>& Light::position() const {
	return position_;
}

//! Accessor to the position of the light (read-write)
/*!
 @return A reference to the position of the light
 */
Point3d<float>& Light::position() {
	return position_;
}

//! Accessor to the infinite flag of the light (read only)
/*!
 *  If the infinite flag of the light is set to true, the light is considered
 *  as being at an "infinite" (very far) distance of the scene. For instance,
 *  the sun can be considered as an infinite light in a game. If the infinite
 *  flag of the light is set to false, the position of the light IS the specified
 *  3D position of the Light object.
 *
 @return The infinite flag of the light
 */
bool Light::infinite() const {
	return infinite_;
}

//! Accessor to the infinite flag of the light (read-write)
/*!
 *  If the infinite flag of the light is set to true, the light is considered
 *  as being at an "infinite" (very far) distance of the scene. For instance,
 *  the sun can be considered as an infinite light in a game. If the infinite
 *  flag of the light is set to false, the position of the light IS the specified
 *  3D position of the Light object.
 *
 @return A reference to the infinite flag of the light
 */
bool& Light::infinite() {
	return infinite_;
}

//! Accessor to the ambient color of the light (read only)
/*!
 @return A constant reference to the ambient color of the light
 */
const ColorRGBA& Light::ambient_color() const {
	return acolor_;
}

//! Set the ambient color and activates the ambient parameter of the light
/*!
 @param ambient The new ambient color of the light
 */
void Light::set_ambient_color(const ColorRGBA& ambient) {
	acolor_ = ambient;
	set_active_parameter(AmbientLight);
}

//! Accessor to the diffuse color of the light (read only)
/*!
 @return A constant reference to the diffuse color of the light
 */
const ColorRGBA& Light::diffuse_color() const {
	return dcolor_;
}

//! Set the diffuse color and activates the diffuse parameter of the light
/*!
 @param diffuse The new diffuse color of the light
 */
void Light::set_diffuse_color(const ColorRGBA& diffuse) {
	dcolor_ = diffuse;
	set_active_parameter(DiffuseLight);
}

//! Accessor to the specular color of the light (read only)
/*!
 @return A constant reference to the specular color of the light
 */
const ColorRGBA& Light::specular_color() const {
	return scolor_;
}

//! Set the specular color and activates the specular parameter of the light
/*!
 @param specular The new specular color of the light
 */
void Light::set_specular_color(const ColorRGBA& specular) {
	scolor_ = specular;
	set_active_parameter(SpecularLight);
}

//! Accessor to the spot direction of the light (read only)
/*!
 @return A constant reference to the spot direction of the light
 */
const Vector3d<float>& Light::spot_direction() const {
	return spot_direction_;
}

//! Set the spot direction and activates the spot direction parameter of the light
/*!
 @param direction The new spot direction of the light
 */
void Light::set_spot_direction(const Vector3d<float>& direction) {
	spot_direction_ = direction;
	set_active_parameter(SpotDirectionLight);
}

//! Accessor to the spot exponent of the light (read only)
/*!
 @return The spot exponent of the light
 */
float Light::spot_exponent() const {
	return spot_exponent_;
}

//! Set the spot exponent and activates the spot exponent parameter of the light
/*!
 @param exponent The new spot exponent of the light
 */
void Light::set_spot_exponent(float exponent) {
	spot_exponent_ = exponent;
	set_active_parameter(SpotExponentLight);
}

//! Accessor to the spot cutoff of the light (read only)
/*!
 @return A constant reference to the spot cutoff of the light
 */
const Angle<float>& Light::spot_cutoff() const {
	return spot_cutoff_;
}

//! Set the spot cutoff and activates the spot cutoff parameter of the light
/*!
 @param cutoff The new spot cutoff of the light
 */
void Light::set_spot_cutoff(const Angle<float>& cutoff) {
	spot_cutoff_ = cutoff;
	set_active_parameter(SpotCutoffLight);
}

//! Accessor to the constant attenuation of the light (read only)
/*!
 @return The constant attenuation of the light
 */
float Light::constant_attenuation() const {
	return constant_attenuation_;
}

//! Set the constant attenuation of the light and activates the constant attenuation parameter of the light
/*!
 @param attenuation The new constant attenuation of the light
 */
void Light::set_constant_attenuation(float attenuation) {
	constant_attenuation_ = attenuation;
	set_active_parameter(AttenuationConstantLight);
}

//! Accessor to the linear attenuation of the light (read only)
/*!
 @return The linear attenuation of the light
 */
float Light::linear_attenuation() const {
	return linear_attenuation_;
}

//! Set the linear attenuation of the light and activates the linear attenuation parameter of the light
/*!
 @param attenuation The new linear attenuation of the light
 */
void Light::set_linear_attenuation(float attenuation) {
	linear_attenuation_ = attenuation;
	set_active_parameter(AttenuationLinearLight);
}

//! Accessor to the quadratic attenuation of the light (read only)
/*!
 @return The quadratic attenuation of the light
 */
float Light::quadratic_attenuation() const {
	return quadratic_attenuation_;
}

//! Set the quadratic attenuation of the light and activates the quadratic attenuation parameter of the light
/*!
 @param attenuation The new quadratic attenuation of the light
 */
void Light::set_quadratic_attenuation(float attenuation) {
	quadratic_attenuation_ = attenuation;
	set_active_parameter(AttenuationQuadraticLight);
}

//! Test if the light parameter is active or not
/*!
 @param parameter The light parameter to test
 @return True if the light parameter is active, false otherwise
 */
bool Light::active_parameter(LightParameter parameter) const {
	return parameters_.testFlag(parameter);
}

//! Set the light parameter active or inactive
/*!
 @param parameter The light parameter to activate / desactivate
 @param enable If set to true, the light parameter should be activated. Otherwise,
               it should be desactivated.
 @return True if the parameter could be activated / desactivated, false otherwise
 */
bool Light::set_active_parameter(LightParameter parameter, bool enable) {
	// Check if parameter is already activated / desactivated
	if ( active_parameter(parameter) == enable ) return false;

	// Activate / desactivate parameter
	parameters_ ^= parameter;
	return true;
}

//! Apply the light parameters
/*!
 @param light_index The index of the light in the list of active lights of the scene
 */
void Light::apply_light(int light_index) const {
	// Setup the position array
	float pos[4] = { position_.x(), position_.y(), position_.z(), 1.0f };
	if ( infinite_ == true ) pos[3] = 0.0f;
	GLenum lenum = GL_LIGHT0 + light_index;
	glLightfv(lenum, GL_POSITION, pos);

	// Setup the ambient color array
	if ( parameters_.testFlag(AmbientLight) ) {
		float ambient_color[4] = { acolor_.r(), acolor_.g(), acolor_.b(), acolor_.a() };
		glLightfv(lenum, GL_AMBIENT, ambient_color);
	}

	// Setup the diffuse color array
	if ( parameters_.testFlag(DiffuseLight) ) {
		float diffuse_color[4] = { dcolor_.r(), dcolor_.g(), dcolor_.b(), dcolor_.a() };
		glLightfv(lenum, GL_DIFFUSE, diffuse_color);
	}

	// Setup the specular color array
	if ( parameters_.testFlag(SpecularLight) ) {
		float specular_color[4] = { scolor_.r(), scolor_.g(), scolor_.b(), scolor_.a() };
		glLightfv(lenum, GL_SPECULAR, specular_color);
	}

	// Setup the spot direction array
	if ( parameters_.testFlag(SpotDirectionLight) ) {
		float spot_direction[3] = { spot_direction_.x(), spot_direction_.y(), spot_direction_.z() };
		glLightfv(lenum, GL_SPOT_DIRECTION, spot_direction);
	}

	// Setup the spot exponent
	if ( parameters_.testFlag(SpotExponentLight) ) {
		glLightf(lenum, GL_SPOT_EXPONENT, spot_exponent_);
	}

	// Setup the spot cutoff
	if ( parameters_.testFlag(SpotCutoffLight) ) {
		glLightf(lenum, GL_SPOT_CUTOFF, spot_cutoff_.deg());
	}

	// Setup the constant light attenuation
	if ( parameters_.testFlag(AttenuationConstantLight) ) {
		glLightf(lenum, GL_CONSTANT_ATTENUATION, constant_attenuation_);
	}

	// Setup the linear light attenuation
	if ( parameters_.testFlag(AttenuationLinearLight) ) {
		glLightf(lenum, GL_LINEAR_ATTENUATION, linear_attenuation_);
	}

	// Setup the quadratic light attenuation
	if ( parameters_.testFlag(AttenuationQuadraticLight) ) {
		glLightf(lenum, GL_QUADRATIC_ATTENUATION, quadratic_attenuation_);
	}

	// Activate the light
	glEnable(GL_LIGHT0+light_index);
}

//=================================================================================

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
